home *** CD-ROM | disk | FTP | other *** search
/ isnet Internet / Isnet Internet CD.iso / prog / hiz / 09 / 09.exe / adynware.exe / perl / lib / site / HTML / Element.pm < prev    next >
Encoding:
Perl POD Document  |  1999-12-28  |  11.5 KB  |  587 lines

  1. package HTML::Element;
  2.  
  3.  
  4. =head1 NAME
  5.  
  6. HTML::Element - Class for objects that represent HTML elements
  7.  
  8. =head1 SYNOPSIS
  9.  
  10.  require HTML::Element;
  11.  $a = new HTML::Element 'a', href => 'http://www.oslonett.no/';
  12.  $a->push_content("Oslonett AS");
  13.  
  14.  $tag = $a->tag;
  15.  $tag = $a->starttag;
  16.  $tag = $a->endtag;
  17.  $ref = $a->attr('href');
  18.  
  19.  $links = $a->extract_links();
  20.  
  21.  print $a->as_HTML;
  22.  
  23. =head1 DESCRIPTION
  24.  
  25. Objects of the HTML::Element class can be used to represent elements
  26. of HTML.  These objects have attributes and content.  The content is an
  27. array of text segments and other HTML::Element objects.  Thus a
  28. tree of HTML::Element objects as nodes can represent the syntax tree
  29. for a HTML document.
  30.  
  31. The following methods are available:
  32.  
  33. =cut
  34.  
  35.  
  36. use strict;
  37. use Carp ();
  38. use HTML::Entities ();
  39.  
  40. use vars qw($VERSION
  41.         %emptyElement %optionalEndTag %linkElements %boolean_attr
  42.            );
  43.  
  44. $VERSION = sprintf("%d.%02d", q$Revision: 1.35 $ =~ /(\d+)\.(\d+)/);
  45. sub Version { $VERSION; }
  46.  
  47. %emptyElement   = map { $_ => 1 } qw(base link meta isindex
  48.                          img br hr wbr
  49.                          input area param
  50.                         );
  51. %optionalEndTag = map { $_ => 1 } qw(p li dt dd option th tr td);
  52.  
  53. %linkElements =
  54. (
  55.  body   => 'background',
  56.  base   => 'href',
  57.  a      => 'href',
  58.  img    => [qw(src lowsrc usemap)],   # lowsrc is a Netscape invention
  59.  form   => 'action',
  60.  input  => 'src',
  61. 'link'  => 'href',          # need quoting since link is a perl builtin
  62.  frame  => 'src',
  63.  applet => 'codebase',
  64.  area   => 'href',
  65. );
  66.  
  67. %boolean_attr = (
  68.  area   => 'nohref',
  69.  dir    => 'compact',
  70.  dl     => 'compact',
  71.  hr     => 'noshade',
  72.  img    => 'ismap',
  73.  input  => 'checked',
  74.  menu   => 'compact',
  75.  ol     => 'compact',
  76.  option => 'selected',
  77. 'select'=> 'multiple',
  78.  td     => 'nowrap',
  79.  th     => 'nowrap',
  80.  ul     => 'compact',
  81. );
  82.  
  83. =head2 $h = HTML::Element->new('tag', 'attrname' => 'value',...)
  84.  
  85. The object constructor.  Takes a tag name as argument. Optionally,
  86. allows you to specify initial attributes at object creation time.
  87.  
  88. =cut
  89.  
  90.  
  91. sub new
  92. {
  93.     my $class = shift;
  94.     my $tag   = shift;
  95.     Carp::croak("No tag") unless defined $tag or length $tag;
  96.     my $self  = bless { _tag => lc $tag }, $class;
  97.     my($attr, $val);
  98.     while (($attr, $val) = splice(@_, 0, 2)) {
  99.     $val = $attr unless defined $val;
  100.     $self->{lc $attr} = $val;
  101.     }
  102.     if ($tag eq 'html') {
  103.     $self->{'_pos'} = undef;
  104.     }
  105.     $self;
  106. }
  107.  
  108.  
  109.  
  110. =head2 $h->tag()
  111.  
  112. Returns (optionally sets) the tag name for the element.  The tag is
  113. always converted to lower case.
  114.  
  115. =cut
  116.  
  117. sub tag
  118. {
  119.     my $self = shift;
  120.     if (@_) {
  121.     $self->{'_tag'} = lc $_[0];
  122.     } else {
  123.     $self->{'_tag'};
  124.     }
  125. }
  126.  
  127.  
  128.  
  129. =head2 $h->starttag()
  130.  
  131. Returns the complete start tag for the element.  Including leading
  132. "<", trailing ">" and attributes.
  133.  
  134. =cut
  135.  
  136. sub starttag
  137. {
  138.     my $self = shift;
  139.     my $name = $self->{'_tag'};
  140.     my $tag = "<\U$name";
  141.     for (sort keys %$self) {
  142.     next if /^_/;
  143.     my $val = $self->{$_};
  144.     if ($_ eq $val &&
  145.         exists($boolean_attr{$name}) && $boolean_attr{$name} eq $_) {
  146.         $tag .= " \U$_";
  147.     } else {
  148.         if ($val !~ /^\d+$/) {
  149.         if (($val =~ tr/\"/\"/) > ($val =~ tr/\'/\'/)) {
  150.             HTML::Entities::encode_entities($val, "&'>");
  151.             $val = qq('$val');
  152.         } else {
  153.             HTML::Entities::encode_entities($val, '&">');
  154.             $val = qq{"$val"};
  155.         }
  156.         }
  157.         $tag .= qq{ \U$_\E=$val};
  158.     }
  159.     }
  160.     "$tag>";
  161. }
  162.  
  163.  
  164.  
  165. =head2 $h->endtag()
  166.  
  167. Returns the complete end tag.  Includes leading "</" and the trailing
  168. ">".
  169.  
  170. =cut
  171.  
  172. sub endtag
  173. {
  174.     "</\U$_[0]->{'_tag'}>";
  175. }
  176.  
  177.  
  178.  
  179. =head2 $h->parent([$newparent])
  180.  
  181. Returns (optionally sets) the parent for this element.
  182.  
  183. =cut
  184.  
  185. sub parent
  186. {
  187.     my $self = shift;
  188.     if (@_) {
  189.     $self->{'_parent'} = $_[0];
  190.     } else {
  191.     $self->{'_parent'};
  192.     }
  193. }
  194.  
  195.  
  196.  
  197. =head2 $h->implicit([$bool])
  198.  
  199. Returns (optionally sets) the implicit attribute.  This attribute is
  200. used to indicate that the element was not originally present in the
  201. source, but was inserted in order to conform to HTML strucure.
  202.  
  203. =cut
  204.  
  205. sub implicit
  206. {
  207.     shift->attr('_implicit', @_);
  208. }
  209.  
  210.  
  211.  
  212. =head2 $h->is_inside('tag',...)
  213.  
  214. Returns true if this tag is contained inside one of the specified tags.
  215.  
  216. =cut
  217.  
  218. sub is_inside
  219. {
  220.     my $self = shift;
  221.     my $p = $self;
  222.     while (defined $p) {
  223.     my $ptag = $p->{'_tag'};
  224.     for (@_) {
  225.         return 1 if $ptag eq $_;
  226.     }
  227.     $p = $p->{'_parent'};
  228.     }
  229.     0;
  230. }
  231.  
  232.  
  233.  
  234. =head2 $h->pos()
  235.  
  236. Returns (and optionally sets) the current position.  The position is a
  237. reference to a HTML::Element object that is part of the tree that has
  238. the current object as root.  This restriction is not enforced when
  239. setting pos(), but unpredictable things will happen if this is not
  240. true.
  241.  
  242.  
  243. =cut
  244.  
  245. sub pos
  246. {
  247.     my $self = shift;
  248.     my $pos = $self->{'_pos'};
  249.     if (@_) {
  250.     $self->{'_pos'} = $_[0];
  251.     }
  252.     return $pos if defined($pos);
  253.     $self;
  254. }
  255.  
  256.  
  257.  
  258. =head2 $h->attr('attr', [$value])
  259.  
  260. Returns (and optionally sets) the value of some attribute.
  261.  
  262. =cut
  263.  
  264. sub attr
  265. {
  266.     my $self = shift;
  267.     my $attr = lc shift;
  268.     my $old = $self->{$attr};
  269.     if (@_) {
  270.     $self->{$attr} = $_[0];
  271.     }
  272.     $old;
  273. }
  274.  
  275.  
  276.  
  277. =head2 $h->content()
  278.  
  279. Returns the content of this element.  The content is represented as a
  280. reference to an array of text segments and references to other
  281. HTML::Element objects.
  282.  
  283. =cut
  284.  
  285. sub content
  286. {
  287.     shift->{'_content'};
  288. }
  289.  
  290.  
  291.  
  292. =head2 $h->is_empty()
  293.  
  294. Returns true if there is no content.
  295.  
  296. =cut
  297.  
  298. sub is_empty
  299. {
  300.     my $self = shift;
  301.     !exists($self->{'_content'}) || !@{$self->{'_content'}};
  302. }
  303.  
  304.  
  305.  
  306. =head2 $h->insert_element($element, $implicit)
  307.  
  308. Inserts a new element at current position and updates pos() to point
  309. to the inserted element.  Returns $element.
  310.  
  311. =cut
  312.  
  313. sub insert_element
  314. {
  315.     my($self, $tag, $implicit) = @_;
  316.     my $e;
  317.     if (ref $tag) {
  318.     $e = $tag;
  319.     $tag = $e->tag;
  320.     } else {
  321.     $e = new HTML::Element $tag;
  322.     }
  323.     $e->{'_implicit'} = 1 if $implicit;
  324.     my $pos = $self->{'_pos'};
  325.     $pos = $self unless defined $pos;
  326.     $pos->push_content($e);
  327.     unless ($emptyElement{$tag}) {
  328.     $self->{'_pos'} = $e;
  329.     $pos = $e;
  330.     }
  331.     $pos;
  332. }
  333.  
  334.  
  335. =head2 $h->push_content($element_or_text,...)
  336.  
  337. Adds to the content of the element.  The content should be a text
  338. segment (scalar) or a reference to a HTML::Element object.
  339.  
  340. =cut
  341.  
  342. sub push_content
  343. {
  344.     my $self = shift;
  345.     $self->{'_content'} = [] unless exists $self->{'_content'};
  346.     my $content = $self->{'_content'};
  347.     for (@_) {
  348.     if (ref $_) {
  349.         $_->{'_parent'} = $self;
  350.         push(@$content, $_);
  351.     } else {
  352.         if (@$content && !ref $content->[-1]) {
  353.         $content->[-1] .= $_;
  354.         } else {
  355.         push(@$content, $_);
  356.         }
  357.     }
  358.     }
  359.     $self;
  360. }
  361.  
  362.  
  363.  
  364. =head2 $h->delete_content()
  365.  
  366. Clears the content.
  367.  
  368. =cut
  369.  
  370. sub delete_content
  371. {
  372.     my $self = shift;
  373.     for (@{$self->{'_content'}}) {
  374.     $_->delete if ref $_;
  375.     }
  376.     delete $self->{'_content'};
  377.     $self;
  378. }
  379.  
  380.  
  381.  
  382. =head2 $h->delete()
  383.  
  384. Frees memory associated with the element and all children.  This is
  385. needed because perl's reference counting does not work since we use
  386. circular references.
  387.  
  388. =cut
  389.  
  390. sub delete
  391. {
  392.     $_[0]->delete_content;
  393.     delete $_[0]->{'_parent'};
  394.     delete $_[0]->{'_pos'};
  395.     $_[0] = undef;
  396. }
  397.  
  398.  
  399.  
  400. =head2 $h->traverse(\&callback, [$ignoretext])
  401.  
  402. Traverse the element and all of its children.  For each node visited, the
  403. callback routine is called with the node, a startflag and the depth as
  404. arguments.  If the $ignoretext parameter is true, then the callback
  405. will not be called for text content.  The flag is 1 when we enter a
  406. node and 0 when we leave the node.
  407.  
  408. If the returned value from the callback is false then we will not
  409. traverse the children.
  410.  
  411. =cut
  412.  
  413. sub traverse
  414. {
  415.     my($self, $callback, $ignoretext, $depth) = @_;
  416.     $depth ||= 0;
  417.  
  418.     if (&$callback($self, 1, $depth)) {
  419.     for (@{$self->{'_content'}}) {
  420.         if (ref $_) {
  421.         $_->traverse($callback, $ignoretext, $depth+1);
  422.         } else {
  423.         &$callback($_, 1, $depth+1) unless $ignoretext;
  424.         }
  425.     }
  426.     &$callback($self, 0, $depth) unless $emptyElement{$self->{'_tag'}};
  427.     }
  428.     $self;
  429. }
  430.  
  431.  
  432.  
  433. =head2 $h->extract_links([@wantedTypes])
  434.  
  435. Returns links found by traversing the element and all of its children.
  436. The return value is a reference to an array.  Each element of the
  437. array is an array with 2 values; the link value and a reference to the
  438. corresponding element.
  439.  
  440. You might specify that you just want to extract some types of links.
  441. For instance if you only want to extract <a href="..."> and <img
  442. src="..."> links you might code it like this:
  443.  
  444.   for (@{ $e->extract_links(qw(a img)) }) {
  445.       ($link, $linkelem) = @$_;
  446.       ...
  447.   }
  448.  
  449. =cut
  450.  
  451. sub extract_links
  452. {
  453.     my $self = shift;
  454.     my %wantType; @wantType{map { lc $_ } @_} = (1) x @_;
  455.     my $wantType = scalar(@_);
  456.     my @links;
  457.     $self->traverse(
  458.     sub {
  459.         my($self, $start, $depth) = @_;
  460.         return 1 unless $start;
  461.         my $tag = $self->{'_tag'};
  462.         return 1 if $wantType && !$wantType{$tag};
  463.         my $attr = $linkElements{$tag};
  464.         return 1 unless defined $attr;
  465.         $attr = [$attr] unless ref $attr;
  466.             for (@$attr) {
  467.            my $val = $self->attr($_);
  468.            push(@links, [$val, $self]) if defined $val;
  469.             }
  470.         1;
  471.     }, 'ignoretext');
  472.     \@links;
  473. }
  474.  
  475.  
  476.  
  477. =head2 $h->dump()
  478.  
  479. Prints the element and all its children to STDOUT.  Mainly useful for
  480. debugging.  The structure of the document is shown by indentation (no
  481. end tags).
  482.  
  483. =cut
  484.  
  485. sub dump
  486. {
  487.     my $self = shift;
  488.     my $depth = shift || 0;
  489.     print STDERR "  " x $depth;
  490.     print STDERR $self->starttag, "\n";
  491.     for (@{$self->{'_content'}}) {
  492.     if (ref $_) {
  493.         $_->dump($depth+1);
  494.     } else {
  495.         print STDERR "  " x ($depth + 1);
  496.         print STDERR qq{"$_"\n};
  497.     }
  498.     }
  499. }
  500.  
  501.  
  502.  
  503. =head2 $h->as_HTML()
  504.  
  505. Returns a string (the HTML document) that represents the element and
  506. its children.
  507.  
  508. =cut
  509.  
  510.  
  511. sub as_HTML
  512. {
  513.     my $self = shift;
  514.     my @html = ();
  515.     my $tag;
  516.     my $enc = 1;
  517.     $self->traverse(sub {
  518.     my($node, $start, $depth) = @_;
  519.     if (ref $node) {
  520.         $tag = $node->tag;
  521.         if ($start) {
  522.         if ($tag eq "script") { 
  523.             $enc = 0;
  524.         }
  525.         push(@html, $node->starttag);
  526.         } else {
  527.         if ($tag eq "script") {
  528.             $enc = 1;
  529.         }
  530.         if (not ($emptyElement{$tag} or $optionalEndTag{$tag})) {
  531.             push(@html, $node->endtag);
  532.         }
  533.         }
  534.     } else {
  535.         if ($enc) { 
  536.           HTML::Entities::encode_entities($node, "<>&");
  537.         }
  538.         push(@html, $node);
  539.     }
  540.     });
  541.     join("\n", @html, "\n");
  542.  
  543. }
  544.  
  545. sub format
  546. {
  547.     my($self, $formatter) = @_;
  548.     unless (defined $formatter) {
  549.     require HTML::FormatText;
  550.     $formatter = new HTML::FormatText;
  551.     }
  552.     $formatter->format($self);
  553. }
  554.  
  555.  
  556. 1;
  557.  
  558. __END__
  559.  
  560.  
  561. =head1 BUGS
  562.  
  563. If you want to free the memory assosiated with a tree built of
  564. HTML::Element nodes then you will have to delete it explicitly.  The
  565. reason for this is that perl currently has no proper garbage
  566. collector, but depends on reference counts in the objects.  This
  567. scheme fails because the parse tree contains circular references
  568. (parents have references to their children and children have a
  569. reference to their parent).
  570.  
  571. =head1 SEE ALSO
  572.  
  573. L<HTML::AsSubs>
  574.  
  575. =head1 COPYRIGHT
  576.  
  577. Copyright 1995,1996 Gisle Aas.  All rights reserved.
  578.  
  579. This library is free software; you can redistribute it and/or
  580. modify it under the same terms as Perl itself.
  581.  
  582. =head1 AUTHOR
  583.  
  584. Gisle Aas <aas@sn.no>
  585.  
  586. =cut
  587.